在前一篇原子交易(Atomic Transaction)中,我們已經看過交易如何確保狀態一致性。
這一篇要更深入探討 Scheduler(調度器)的設計,說明不同策略的取捨,並對照我們的程式碼在流程圖中落在哪條路徑。
Scheduler 是 reactivity 系統中的「隱形指揮官」
簡單來說,Scheduler 不是「要不要算」,而是「何時算」。
大致可以分為以下幾類:
這種差異會影響系統的整體策略:

這張圖展示了從「資料更新」到「UI 更新」的不同策略路徑。
| 策略 | 觸發時機 | 核心資料流 | 優點 | 代價 / 風險 | 適用情境 | 常見實例 | 
|---|---|---|---|---|---|---|
| 同步調度 (Sync) | set()後立即執行 | write → compute → effect | 心智直觀、除錯容易 | 重複運算、互動抖動 | 極簡頁面、更新頻率低 | 小型框架、教學用 PoC | 
| 批次調度 (Batch) | 同一 tick/microtask 末尾一次 flush | write × N → queue → flush | 減少重繪、效能佳 | 延遲一致、需要 queue | 表單互動、動畫外層 | React batched updates、Vue job queue | 
| 優先級調度 (Priority) | 依 deadline / 重要性切片 | enqueue(prio) → run until deadline | 不阻塞高價值互動 | 複雜度高、可能「飢餓」 | 長列表、並行渲染 | React Concurrent、 startTransition | 
| Lazy | 被讀取時才計算 | write → mark-dirty → read 才算 | 寫入代價低 | 首次讀取延遲 | 讀多寫少 | Signals memo/computed | 
| Eager | 寫入當下標記下游 | write → mark downstream → flush | 讀取更快、失效清楚 | 寫入代價高 | 寫少讀多、強一致 UI | Solid/Preact Signals | 
| 框架 | 調度模式 | 策略 | 
|---|---|---|
| React | Batch + Priority | microtask queue + concurrent | 
| Vue 3 | Batch | job queue | 
| Solid | Microtask Batch (Eager) | 每個更新皆標記並批次刷新 | 
| Signals (Preact/Angular) | Push + Lazy | 僅在讀取時才計算依賴 | 
可以看出不同框架對 Scheduler 的取捨,直接影響到「效能優化」與「心智模型」的差異。
在 Atomic Transaction 章節,我們有三個重要情境:
begin → writes → commit資料更新 → 批次調度 + Eager 標記 → flush → 副作用
begin → writes → rollback資料更新 → (尚未 flush) → 回滾 → 標髒下游 → 下次讀取才重算
signal.set() → 寫入markStale(node) → Eager 標記track()/link()/unlink() → 依賴追蹤queueMicrotask(flush) → Batch flushflush() → 拓撲重算 + runEffectsrollback() → 還原快照 + 標記髒值atomic() / transaction()在上一篇中,為了方便大家對照,所以我保留了原本 transaction() 的部分,但其實應該要使用 atomic() 的邏輯取代掉 transaction() 的,從程式碼來看,具備完整交易語意的是 atomic():
flushJobs()(屬於 Batch + Eager)。而目前的 transaction() 僅等同於「批次 (batch)」,缺乏 rollback,語意與前文不同。
transaction 與前文語意一致,建議直接包裝成 atomic() 的別名:export function transaction<T>(fn: () => T): T;
export function transaction<T>(fn: () => Promise<T>): Promise<T>;
export function transaction<T>(fn: () => T | Promise<T>): T | Promise<T> {
  return atomic(fn);
}
scheduleJob() 加入 muted 判斷,避免 rollback 期間排進新任務:export function scheduleJob(job: Schedulable) {
  if (job.disposed) return;
  if (muted > 0) return; // 回滾/靜音期間不進隊列
  queue.add(job);
  if (!scheduled && batchDepth === 0) {
    scheduled = true;
    queueMicrotask(flushJobs);
  }
}
其他部分不需要更動,改了這個只是讓行為跟上述策略描述上對齊。
一個有趣的問題是:
開發者究竟需不需要知道 Scheduler 的存在?
因此,我們可以這樣總結:
Scheduler 不僅是效能優化的工具,也是一種「開發者體驗」的設計。
進階的 Scheduler 可能還會包含:
這些設計概念其實與作業系統的「排程器」如出一轍,只是應用在前端 reactivity 的脈絡中。
Scheduler 在 reactivity 系統中,扮演著「隱形指揮官」的角色。
它不僅決定了效能的上限,也影響了開發者的心智模型。
在不同框架的取捨背後,其實都圍繞著同一個問題:
「我們希望在什麼時機,付出多少代價,去換取 UI 的即時一致性?」
下一篇,我們來談談 Scheduler 在記憶體與圖管理上面臨的議題。